#ifndef GAME_STATE_H
#define GAME_STATE_H

#include <vector>
#include <functional>
#include <memory>
#include <sstream>
#include <deque>

#include "Common.h"
#include "Ant.h"
#include "EmptyCell.h"
#include "Water.h"
#include "Food.h"
#include "AntHill.h"
#include "Move.h"

using namespace std;

class GameState
{
private:
    vector <shared_ptr<ACell>> field;

    shared_ptr<ACell> & getXYPtr(int32_t aRow, int32_t aColumn)
    {
        return this->field.at(aRow * this->columnCount + aColumn);
    }

public:

    int32_t rowCount;
    int32_t columnCount;
    // массивы указателей на еду, моих муравьёв и муравейники. очищаются каждый ход
    set <shared_ptr<Ant>, ACellPtrCompare> myAnts;
    set <shared_ptr<Ant>, ACellPtrCompare> enemyAnts;
    set <shared_ptr<AntHill>, ACellPtrCompare> myAntHills;
    set <shared_ptr<AntHill>, ACellPtrCompare> enemyAntHills;
    set <shared_ptr<Food>, ACellPtrCompare> foods;
    deque <Move> moves;
    vector<string> filteredMovies;

    GameState(int32_t sizeRow, int32_t sizeColumn)
    {
        this->rowCount = sizeRow;
        this->columnCount = sizeColumn;

        for (auto i = 0; i < sizeRow; i++)
        {
            for (auto j = 0; j < sizeColumn; j++)
            {
                this->field.push_back(shared_ptr<ACell>(new EmptyCell(i, j)));
            }
        }
    }

    ACell * getXY(int32_t row, int32_t cell)
    {
        return this->getXYPtr(row, cell).get();
    }

    /*
     * 	Говорит, куда ты попадёшь из клетки после move
     * 		Корректные move: 'n', 'w', 'e', 's', 'o' (т.е. никуда не идти)
     */
    void getXYAfterMove(const int32_t & oldRow, const int32_t & oldColumn,
            int32_t & newRow, int32_t & newColumn, const char & move) const
    {
        int32_t nRow, nColumn;
        if (move == 'n')
        {
            nRow = oldRow - 1;
            nColumn = oldColumn;
        }
        else if (move == 's')
        {
            nRow = (oldRow + 1) % this->rowCount;
            nColumn = oldColumn;
        }
        else if (move == 'w')
        {
            nColumn = oldColumn - 1;
            nRow = oldRow;
        }
        else if (move == 'e')
        {
            nColumn = (oldColumn + 1) % this->columnCount;
            nRow = oldRow;
        }
        else
        {
            nColumn = oldColumn;
            nRow = oldRow;
        }


        if (nRow < 0) nRow += this->rowCount;
        if (nColumn < 0) nColumn += this->columnCount;

        newRow = nRow;
        newColumn = nColumn;
    }

    /*
     * 	Возвращает 'n', 'w', 'e' или 's' если из begin можно попасть в end
     * 		иначе возвращает 'o'
     */
    char getMove(const ACell * pBegin, const ACell * pEnd)
    {
        const int32_t rowBegin = pBegin->row();
        const int32_t columnBegin = pBegin->column();
        const int32_t rowEnd = pEnd->row();
        const int32_t columnEnd = pEnd->column();
        char aMoves[] = {'n', 's', 'e', 'w'};

        for (char move : aMoves)
        {
            int32_t tmpRow, tmpColumn;
            getXYAfterMove(rowBegin, columnBegin, tmpRow, tmpColumn, move);

            if ((tmpRow == rowEnd) && (tmpColumn == columnEnd))
            {
                return move;
            }
        }

        return 'o';

    }


    // последний параметр не обязательно указывать (для того кого он не нужен)

    void setXY(int32_t row, int32_t column, CellType ct, bool isMy = false)
    {
        switch (ct)
        {
            case EMPTY:
            {
                EmptyCell ec(row, column);
                this->setXY(ec);
            }
            break;
            case ANT:
            {
                Ant ant(row, column, isMy);
                this->setXY(ant);
            }
            break;
            case WATER:
            {
                Water water(row, column);
                this->setXY(water);
            }
            break;
            case ANTHILL:
            {
                AntHill ah(row, column, isMy);
                this->setXY(ah);
            }
            break;
            case FOOD:
            {
                Food food(row, column);
                this->setXY(food);
            }
            break;
        }
    }

    void setXY(const Ant & ant)
    {
        shared_ptr<ACell> & a = this->getXYPtr(ant.row(), ant.column());
        a.reset(new Ant(ant));
        if (ant.isMyAnt())
            this->myAnts.insert(shared_ptr<Ant>(new Ant(ant)));
        else
            this->enemyAnts.insert(shared_ptr<Ant>(new Ant(ant)));
        // = shared_ptr<ACell>(new Ant(a));
    }

    void setXY(const EmptyCell & ec)
    {
        shared_ptr<ACell> & e = this->getXYPtr(ec.row(), ec.column());
        e.reset(new EmptyCell(ec));
    }

    void setXY(const Water & w)
    {
        shared_ptr<ACell> & water = this->getXYPtr(w.row(), w.column());
        water.reset(new Water(w));
    }

    void setXY(const Food & f)
    {
        shared_ptr<ACell> & food = this->getXYPtr(f.row(), f.column());
        food.reset(new Food(f));
        this->foods.insert(shared_ptr<Food>(new Food(f)));
    }

    void setXY(const AntHill & ah)
    {
        if (this->getXY(ah.row(), ah.column())->getType() != ANT)
        {
            shared_ptr<ACell> & antHill = this->getXYPtr(ah.row(), ah.column());
            antHill.reset(new AntHill(ah));
        }
        if (ah.isMyAntHill())
            this->myAntHills.insert(shared_ptr<AntHill>(new AntHill(ah)));
        else
            this->enemyAntHills.insert(shared_ptr<AntHill>(new AntHill(ah)));
    }

    /*
     *  удаляет с карты все "временные" объекты - всё что может пропасть в след. ходу и
     *  что подаётся на кажом ходу на вход, а именно: муравьёв, 
     *  т.е. муравьёв, еду и муравейники
     */
    void cleanAfterTurn()
    {
        for (shared_ptr<ACell> & it : this->field)
        {
            if (it->getType() != WATER)
            {
                it.reset(new EmptyCell(it->row(), it->column()));
            }
        }
        // а теперь очищаем массивы с "кэшем"
        this->myAnts.clear();
        this->enemyAnts.clear();
        this->myAntHills.clear();
        this->enemyAnts.clear();
        this->foods.clear();
        this->moves.clear();
        this->filteredMovies.clear();
    }
  
    /*
     * Печатает GameState в cerr
     * @ еда
     * x муравей
     * ~ вода
     * ^ муравейник
     * 
     */
    void printState()
    {
        for (auto i = 0; i < this->rowCount; i++)
        {
            for (auto j = 0; j < this->columnCount; j++)
            {
                ACell * ptr = this->getXY(i, j);

                if (ptr->getType() == ANT)
                {
                    if (reinterpret_cast<Ant*>(ptr)->getAntState() == GOEZ_FOR_FOOD)
                    {
                        cerr << 'G';
                    }
                    else
                        cerr << "x";
                } 
                else if (ptr->getType() == WATER)
                    cerr << "~";
                else if (ptr->getType() == FOOD)
                    cerr << "@";
                else if (ptr->getType() == ANTHILL)
                    cerr << "^";
                else
                    cerr << " ";
            }
            cerr << endl << flush;
        }
    }

    void addMove(Ant * a, char move)
    {
        if (move != 'o')
        {
            const int32_t antRow = a->row();
            const int32_t antColumn = a->column();
            int32_t destRow, destColumn;
            this->getXYAfterMove(antRow, antColumn, destRow, destColumn, move);
            Coord dest = {destRow, destColumn};
            Move m(*a, dest, move);
            this->moves.push_back(m);
        }
    }

    void addMove(Ant & ant, char move)
    {
        this->addMove(&ant, move);
    }

    /*
    bool antCanMakeMove(const Ant & ant)
    {
        
         * 	Смотрим клетку муравья.
         * 		если от неё на манхетенском расстоянии 2 есть другой муравей
         * 		с большим приоритетом, то возвращаем false
         
        const int32_t antRow = ant.row();
        const int32_t antColumn = ant.column();

        char cMoves[] = {'n', 'e', 's', 'w'};

        for (auto move : cMoves)
        {
            int32_t moveRow, moveColumn;

            getXYAfterMove(antRow, antColumn, moveRow, moveColumn, move);
            ACell * dest = getXY(moveRow, moveColumn);

            if (dest->getType() == ANT)
            {
                const Ant * destAnt = reinterpret_cast<Ant*> (dest);
                if (destAnt->isMyAnt())
                {
                    // если статус destAnt > статуса ant, то о ходьбе речи нет
                    if (destAnt->getAntState() == GOEZ_FOR_FOOD)
                    {
                        return false;
                    }
                }
            }

        }
        

        return true;
    }
    */
    
    
    /*
     * Говорит, может ли муравей пойти в нужном направлении
     */
    bool antCanGo(const Ant & ant, const char & move)
    {
        const int32_t antRow = ant.row();
        const int32_t antColumn = ant.column();
        
        int32_t newRow, newColumn;
        
        getXYAfterMove(antRow, antColumn, newRow, newColumn, move);
        
        return this->getXY(newRow, newColumn)->isEmpty();
    }
};

#endif /* GAME_STATE_H */
